import sys
import time
import threading
# from multiprocessing import Process,Queue,JoinableQueue
from threading import Thread
from collections import OrderedDict
from PyMimircache import Cachecow
from PyMimircache.cache.asy_lru import ASY_LRU
from PyMimircache.cacheReader.requestItem import Req
from queue import Queue
from PyMimircache.cache.reqblk import REQ_BLK


"""
3. 定义三层结构：
    3.1 从trace读请求到dram_reqin_que，若DRAM命中将数据放入dram_reqout_que，否则将请求的id放入nvm_reqin_que然后执行下一个请求；
    3.2 当nvm_reqin_que不为空时，启动NVM层访问，若命中则将请求放入nvm_reqout_que，否则将请求id放入ssd_reqin_que，当nvm_reqout_que不为空时，将数据更新到dram；
    3.3 当ssd_reqin_que不为空时，在SSD访问，将命中的数据放入ssd_reqout_que，当ssd_reqout_que不为空时，将数据更新到nvm；

    使用三个生产者-消费者模型实现
"""
# ------------------上层--------------------------
# p1. 从trace读数据到dram的入队列
def trace_read(name,tid,trace_path,dram_cache):
    print("enter in ",name,tid)

    global readflag1
    reader=c.open(trace_path)
    n_req=reader.get_num_of_req()
    for n in range(n_req):
        print("readflag is",readflag1)
        readflag1=1
        request=reader.read_one_req()
        req=Req(request)
        req.set_tid(tid)
        # 将请求放入dram的入队列
        while dram_cache.reqin_que.full():
            pass
        dram_cache.reqin_que.put(req)
        print("trace read req is ",req.item_id,"thread is ",req.tid)
        # 保证trace的同步读
        while readflag1:
            pass
        

# p2. dram返回请求给上层
def trace_return(name,dram_cache,reqblk):
    print("enter in ",name)
    global readflag1

    # while True:
    while exitflag:
        if not dram_cache.reqout_que.empty():
            req=dram_cache.reqout_que.get()
            t_id=req.tid
            req_id=req.item_id
            print("dram return",req_id,"tid is ",t_id)

            # 从阻塞队列中消除相应的阻塞请求
            reqblk.reqblk_rm(req)
            # if req_id in dram_cache.reqblk_dict:
            #     dram_reqblk_lock.acquire()
            #     index=dram_cache.reqblk_dict[req_id].index(t_id)
            #     print("[trace return1]",dram_cache.reqblk_dict.items()) 
            #     del dram_cache.reqblk_dict[req_id][index]
            #     if len(dram_cache.reqblk_dict[req_id])==0:
            #         del dram_cache.reqblk_dict[req_id]
            #     print("[trace return2]",dram_cache.reqblk_dict.items()) 
            #     dram_reqblk_lock.release()                                                        
            
            readflag1=0
            
def reqblk_access(name,dram_cache,nvm_cache):
    print("enter in ",name)

    

# ---------------DRAM---------------------------
# p3. dram从入队列读请求，hit放入出队列，miss放入nvm的入队列
def dram_access(name,dram_cache,reqblk,nvm_cache):
    print("enter in ",name)

    #不断访问dram的入队列
    # while True: 
    while exitflag:
        if not dram_cache.reqin_que.empty():
            dram_lock.acquire()
            #dram_reqblk_lock.acquire()
            print("[dram_access]",dram_cache.cacheline_dict.keys())
            # dram_cache.asy_access(nvm_cache.reqin_que,dram_reqblk_lock)
            dram_cache.asy_access(nvm_cache.reqin_que,reqblk)
            #dram_reqblk_lock.release()
            dram_lock.release()
   

# p4. dram处理从nvm promote的数据
def dram_promote(name,dram_cache,reqblk,nvm_cache):
    print("enter in ",name)

    # while True:
    while exitflag:
        if not nvm_cache.reqout_que.empty():
            dram_lock.acquire()
            dram_cache.asy_promote(nvm_cache.reqout_que,reqblk)
            dram_lock.release()
            
# --------------------NVM----------------------------
# p5. NVM从入队列读请求，hit放入出队列，miss放入ssd的入队列
def nvm_access(name,nvm_cache,ssd_cache):
    print("enter in ",name)

    # while True:
    while exitflag:
        if not nvm_cache.reqin_que.empty():
            nvm_lock.acquire()
            print("[nvm_access]",nvm_cache.cacheline_dict.keys())
            nvm_cache.asy_access(ssd_cache.reqin_que)
            nvm_lock.release()


# p6. nvm处理从ssd promote的数据
def nvm_promote(name,nvm_cache,ssd_cache):
    print("enter in ",name)
    
    # while True:
    while exitflag:
        if not ssd_cache.reqout_que.empty():
            nvm_lock.acquire()
            nvm_cache.asy_promote(ssd_cache.reqout_que)
            nvm_lock.release()


# --------------------SSD-------------------------------
# p7. ssd从入队列读请求，hit数据放入出队列
error_que=Queue()
def ssd_access(name,ssd_cache):
    print("enter in ",name)

    # while True:
    while exitflag:
        if not ssd_cache.reqin_que.empty():
            print("[ssd_access]",ssd_cache.cacheline_dict.keys())
            ssd_cache.asy_access(error_que)
            if not error_que.empty():
                print(" ssd back end error!")
                break
    

def test(name,dram_cache,nvm_cache):
    print("enter in ",name)

    while True:
        if not nvm_cache.reqin_que.empty():
            print("[test1]",dram_cache.cacheline_dict.keys())
            req_item=nvm_cache.reqin_que.get()
            if len(dram_cache.cacheline_dict) >= dram_cache.cache_size:
                evict_item = dram_cache.evict()
            dram_cache._insert(req_item)
            print("[test2]",dram_cache.cacheline_dict.keys())


# p1=Process(target=trace_read,args=('trace_read1',trace_path,dram_cache))
# p1.start()

# t8=Thread(target=test,args=('test',dram_cache,nvm_cache))
# t8.start()

"""
2. 定义back_end的ssd，并装入所有的trace数据
"""
def ssd_backend(ssd_cache,reader):
    # 在back_end的ssd中装入全部数据
    n_req=reader.get_num_of_req()
    evict_item_list = []
    for n in range(n_req):
        request=reader.read_one_req()
        # print("request type ",type(request)==type(str(1)),request)
        req=Req(request)
        # print("req type ",type(req))


        if ssd_cache.access(req):
            continue

    print("back end ssd_cache info-------------------------------------------")
    print(ssd_cache)
    print("back end ssd_cache info-------------------------------------------")

class father():
    def talk(self):
        print("in father talking")
        return 4

class child(father):
    def walk(self):
        print("in child walking")
        f=child.talk(self)
        print("f is ",f)

if __name__ == '__main__':
    c=Cachecow()
    # 运行格式：python3 脚本名 trace路径
    trace_path=sys.argv[1]
    reader=c.open(trace_path)

    """
    1. 定义DRAM、NVM、nvm参数：
                容量    速度
        DRAM    16G      80ns
        NVM     128G    300ns
        SSD     512G    150us
    """
    # SSD
    ssd_size=reader.get_num_of_uniq_req()
    ssd_que_size=6

    ssd_cache=ASY_LRU("ssd",cache_size=ssd_size,queue_size=ssd_que_size)

    # NVM
    nvm_size=int(0.25*reader.get_num_of_uniq_req()+1)
    nvm_que_size=5
    nvm_lock = threading.Lock()

    nvm_cache=ASY_LRU("nvm",cache_size=nvm_size,queue_size=nvm_que_size)

    # DRAM
    dram_size=int(0.03*reader.get_num_of_uniq_req())
    if dram_size <1:
        dram_size=1
    dram_que_size=3
    dram_lock = threading.Lock()
    reqblk=REQ_BLK()
    # dram_reqblk_lock= threading.Lock()

    dram_cache=ASY_LRU("dram",cache_size=dram_size,queue_size=dram_que_size)

    print("ssd size is ",ssd_cache.cache_size)
    print("nvm size is ",nvm_cache.cache_size)
    print("dram size is ",dram_cache.cache_size)

    """
    2. 定义back_end的ssd，并装入所有的trace数据
    """
    ssd_backend(ssd_cache,reader)

    reader.reset()


    """
    3. 启动线程
    """
    exitflag=1
    
    # 上层trace的线程数
    trace_num=1
    t1=Thread(target=trace_read, args=('trace_read1',1,trace_path,dram_cache))
    readflag1=1
    readflag1_lock = threading.Lock()
    # 定义上层每个读trace线程的flag
    # thread_flag={}
    # for i in range(trace_num):
    #     flag="readflag"+i
    #     thread_flag[i]=flag
    # print(thread_flag.items())

    t2=Thread(target=trace_return,args=('trace_return',dram_cache,reqblk))
    t3=Thread(target=dram_access,args=('dram_access',dram_cache,reqblk,nvm_cache))
    t4=Thread(target=dram_promote,args=('dram_promote',dram_cache,reqblk,nvm_cache))
    t5=Thread(target=nvm_access,args=('nvm_access',nvm_cache,ssd_cache))
    t6=Thread(target=nvm_promote,args=('nvm_promote',nvm_cache,ssd_cache))
    t7=Thread(target=ssd_access,args=('ssd_access',ssd_cache))
    t8=Thread(target=reqblk_access,args=('reqblk_access',dram_cache,nvm_cache))

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()
    t6.start()
    t7.start()


    time.sleep(60)
    print(dram_cache.cacheline_dict.items())
    exitflag=0

    # dram_cache.cache_size=5
    # dram_cache.access('1')
    # dram_cache.access('5')
    # dram_cache.access('3')
    # dram_cache.access('8')
    # dram_cache.access('2')

    # # dram_cache._update('9')
    # key=dram_cache.cacheline_dict.keys()
    # k_id=OrderedDict()
    # k_id1=OrderedDict()
    # print(key)
    # id='9'
    # flag=0
    # for k in key:
    #     if k==id:
    #         flag=1
    #     if flag:
    #         print(k)
    #         k_id[k]=True

    # print(k_id.items())

    # k_list=[]
    # k_list=list(key)
    # print(k_list)
    # index=k_list.index(id)
    # print(index)
    # start=index
    # end=min(index+3,k_list.__len__())

    # for i in range(start,end):
    #     k_id1[k_list[i]]=True
    # print(k_id1.items())

    # q = Queue()
    # for i in range(5):
    #     q.put(i)
    # if 3 in q.queue:
    #     print("true")
    # for i in q.queue:
    #     print(i)
    # l = list(q.queue)
    # print(l)

    # key=OrderedDict()
    # key[1]='2'
    # key[3]='5'
    # key.setdefault(4,[]).append(1)
    # key.setdefault(4,[]).append(2)

    # for i in key:
    #     print(i)
    # print(key.items())
    # # print(key[4][0])
    # # print(key[4][1])
    # for i in range(2):
    #     print("i:",i)
    #     print(key[4][i])
    # print(len(key[4]))
 
    c=child()
    c.walk()